/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2002-2006 * Sleepycat Software. All rights reserved. * * $Id: SharedLatchImpl.java,v 1.1 2006/05/06 09:00:35 ckaestne Exp $ */ package com.sleepycat.je.latch; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.RunRecoveryException; import com.sleepycat.je.dbi.EnvironmentImpl; /** * Simple thread-based non-transactional reader-writer/shared-exclusive latch. * * Latches provide simple exclusive or shared transient locks on objects. * Latches are expected to be held for short, defined periods of time. No * deadlock detection is provided so it is the caller's responsibility to * sequence latch acquisition in an ordered fashion to avoid deadlocks. * * Nested latches for a single thread are supported, but upgrading a shared * latch to an exclusive latch is not. This implementation is based on the * section Reader-Writer Locks in the book Java Threads by Scott Oaks, 2nd * Edition, Chapter 8. * * This SharedLatch implementation is only used when Java 5 * ReentrantReadWriteLocks are not available. */ public class SharedLatchImpl implements SharedLatch { private String name = null; private List waiters = new ArrayList(); private LatchStats stats = new LatchStats(); /* The object that the latch protects. */ private EnvironmentImpl env = null; private boolean noteLatch; /** * Create a shared latch. */ public SharedLatchImpl(String name, EnvironmentImpl env) { this.name = name; this.env = env; } /** * Set the latch name, used for latches in objects instantiated from the * log. */ synchronized public void setName(String name) { this.name = name; } /** * If noteLatch is true, then track latch usage in the latchTable. */ synchronized public void setNoteLatch(boolean noteLatch) { this.noteLatch = noteLatch; } /** * Acquire a latch for exclusive/write access. Nesting is allowed, that * is, the latch may be acquired more than once by the same thread for * exclusive access. However, if the thread already holds the latch for * shared access, it cannot be upgraded and LatchException will be thrown. * * Wait for the latch if some other thread is holding it. If there are * threads waiting for access, they will be granted the latch on a FIFO * basis. When the method returns, the latch is held for exclusive access. * * @throws LatchException if the latch is already held by the current * thread for shared access. * * @throws RunRecoveryException if an InterruptedException exception * occurs. */ public synchronized void acquireExclusive() throws DatabaseException { try { Thread thread = Thread.currentThread(); int index = indexOf(thread); Owner owner; if (index < 0) { owner = new Owner(thread, Owner.EXCLUSIVE); waiters.add(owner); } else { throw new LatchException (getNameString() + " reentrancy/upgrade not allowed"); } if (waiters.size() == 1) { stats.nAcquiresNoWaiters++; } else { stats.nAcquiresWithContention++; while (waiters.get(0) != owner) { wait(); } } owner.nAcquires += 1; assert (noteLatch ? noteLatch() : true); // intentional side effect } catch (InterruptedException e) { throw new RunRecoveryException(env, e); } finally { assert EnvironmentImpl.maybeForceYield(); } } public boolean acquireExclusiveNoWait() throws DatabaseException { try { Thread thread = Thread.currentThread(); int index = indexOf(thread); if (index < 0) { if (waiters.size() == 0) { Owner owner = new Owner(thread, Owner.EXCLUSIVE); waiters.add(owner); owner.nAcquires += 1; stats.nAcquiresNoWaiters++; /* Intentional side effect. */ assert (noteLatch ? noteLatch() : true); return true; } else { return false; } } else { throw new LatchException (getNameString() + " reentrancy/upgrade not allowed"); } } finally { assert EnvironmentImpl.maybeForceYield(); } } /** * Acquire a latch for shared/read access. Nesting is allowed, that is, * the latch may be acquired more than once by the same thread. * * @throws RunRecoveryException if an InterruptedException exception * occurs. */ public synchronized void acquireShared() throws DatabaseException { try { Thread thread = Thread.currentThread(); int index = indexOf(thread); Owner owner; if (index < 0) { owner = new Owner(thread, Owner.SHARED); waiters.add(owner); } else { owner = (Owner) waiters.get(index); } while (indexOf(thread) > firstWriter()) { wait(); } owner.nAcquires += 1; stats.nAcquireSharedSuccessful++; assert (noteLatch ? noteLatch() : true); // intentional side effect } catch (InterruptedException e) { throw new RunRecoveryException(env, e); } finally { assert EnvironmentImpl.maybeForceYield(); } } /** * Release an exclusive or shared latch. If there are other thread(s) * waiting for the latch, they are woken up and granted the latch. */ public synchronized void release() throws LatchNotHeldException { try { Thread thread = Thread.currentThread(); int index = indexOf(thread); if (index < 0 || index > firstWriter()) { return; } Owner owner = (Owner) waiters.get(index); owner.nAcquires -= 1; if (owner.nAcquires == 0) { waiters.remove(index); /* Intentional side effect. */ assert (noteLatch ? unNoteLatch() : true); notifyAll(); } stats.nReleases++; } finally { assert EnvironmentImpl.maybeForceYield(); } } /** * Returns the index of the first Owner for the given thread, or -1 if * none. */ private int indexOf(Thread thread) { Iterator i = waiters.iterator(); for (int index = 0; i.hasNext(); index += 1) { Owner owner = (Owner) i.next(); if (owner.thread == thread) { return index; } } return -1; } /** * Returns the index of the first Owner waiting for a write lock, or * Integer.MAX_VALUE if none. */ private int firstWriter() { Iterator i = waiters.iterator(); for (int index = 0; i.hasNext(); index += 1) { Owner owner = (Owner) i.next(); if (owner.type == Owner.EXCLUSIVE) { return index; } } return Integer.MAX_VALUE; } /** * Holds the state of a single owner thread. */ private static class Owner { static final int SHARED = 0; static final int EXCLUSIVE = 1; Thread thread; int type; int nAcquires; Owner(Thread thread, int type) { this.thread = thread; this.type = type; } } /* * For concocting exception messages */ private String getNameString() { return LatchSupport.latchTable.getNameString(name); } /** * Only call under the assert system. This records latching by thread. */ private boolean noteLatch() throws LatchException { return LatchSupport.latchTable.noteLatch(this); } /** * Only call under the assert system. This records latching by thread. */ private boolean unNoteLatch() throws LatchNotHeldException { return LatchSupport.latchTable.unNoteLatch(this, name); } public synchronized boolean isWriteLockedByCurrentThread() { if (waiters.size() > 0) { Owner curOwner = (Owner) waiters.get(0); return (curOwner.thread == Thread.currentThread() && curOwner.type == Owner.EXCLUSIVE); } else { return false; } } /******* ** Note needed to implement SharedLatch but left in case we need the ** API in the future. ******/ /** * Return true if the current thread holds this latch. * * @return true if we hold this latch. False otherwise. */ /*********** public boolean isOwner() { return Thread.currentThread() == owner(); } ***********/ /** * Used only for unit tests. * * @return the thread that currently holds the latch for exclusive access. */ /*********** public synchronized Thread owner() { return (waiters.size() > 0) ? ((Owner) waiters.get(0)).thread : null; } ***********/ /** * Return the number of threads waiting. * * @return the number of threads waiting for the latch. */ /*********** public synchronized int nWaiters() { int n = waiters.size(); return (n > 0) ? (n - 1) : 0; } ***********/ /** * Used for debugging latches. * * @return a LatchStats object with information about this latch. */ /*********** public LatchStats getLatchStats() { return stats; } ***********/ /** * Formats a latch owner and waiters. */ /*********** public synchronized String toString() { return LatchSupport.latchTable.toString(name, owner(), waiters, 1); } ***********/ }